namespace Microsoft.Robotics.Core.Algorithms.UnitTests
{
    using System;
    using System.Linq;
    using Microsoft.Robotics.Numerics;
    using Microsoft.VisualStudio.TestTools.UnitTesting;

    /// <summary>
    /// Class for testing the simple sampler
    /// </summary>
    [TestClass]
    public class SamplerTests
    {
        /// <summary>
        /// Floating point operation accuracy threshold
        /// </summary>
        private const double Epsilon = MathConstants.ErrorEpsilon;

        /// <summary>
        /// Initial Values Are Zero When No Samples Are Added
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void InitialValuesAreZeroWhenNoSamplesAreAdded()
        {
            Sampler s = new Sampler();

            Assert.AreEqual(0, s.Mean);
            Assert.AreEqual((uint)0, s.SampleCount);           
            Assert.AreEqual(0, s.Total);
            Assert.AreEqual(0, s.Last);
            Assert.AreEqual(0, s.StdDev);
            Assert.AreEqual(0, s.Variance);
        }

        /// <summary>
        /// Sigma And Variance Are Zero When Only One Sample Is Added
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void SigmaAndVarianceAreZeroWhenOnlyOneSampleIsAdded()
        {
            Sampler s = new Sampler();

            s.Add(5);

            Assert.AreEqual(5, s.Mean);
            Assert.AreEqual((uint)1, s.SampleCount);
            Assert.AreEqual(5, s.Total);
            Assert.AreEqual(5, s.Last);
            Assert.AreEqual(0, s.StdDev);
            Assert.AreEqual(0, s.Variance);
        }

        /// <summary>
        /// Sigma And Variance Are Zero When equal Samples are Added
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void SigmaAndVarianceAreZeroWhenEqualSamplesSampleAreAdded()
        {
            Sampler s = new Sampler();

            s.Add(7);
            s.Add(7);

            Assert.AreEqual(7, s.Mean);
            Assert.AreEqual((uint)2, s.SampleCount);
            Assert.AreEqual(14, s.Total);
            Assert.AreEqual(7, s.Last);
            Assert.AreEqual(0, s.StdDev);
            Assert.AreEqual(0, s.Variance);
        }

        /// <summary>
        /// Sigma And Variance Are Zero When Only One Sample Is Added
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void MeanIsCorrectWhenManySamplesAreAdded()
        {
            Sampler s = new Sampler();

            s.Add(0.05);
            s.Add(0);
            s.Add(10);
            s.Add(100);
            s.Add(1000);

            Assert.IsTrue(Math.Abs(s.Mean - (1000 + 100 + 10 + 0 + 0.05) / 5) < Epsilon);
        }

        /// <summary>
        /// StdDev Is Correctly Calculated When Many Samples Are Added
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void StdDevIsCorrectlyCalculatedWhenManySamplesAreAdded()
        {
            Sampler s = new Sampler();

            s.Add(0.05);
            s.Add(0);
            s.Add(10);
            s.Add(100);
            s.Add(100);

            Assert.IsTrue(Math.Abs(s.Variance - 2818.9505) < Epsilon);
            Assert.IsTrue(Math.Abs(s.StdDev - Math.Sqrt(2818.9505)) < Epsilon);
        }

        /// <summary>
        /// StdDev and Mean are Correctly Calculated When Many Samples Are Added using bulk add
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void StdDevAndMeanAreCorrectlyCalculatedWhenManySamplesAreAddedUsingBulkAdd()
        {
            // Any IEnumerble will do - we just use an array
            double[] array = new double[] { 0.05, 0, 10, 100, 100 };

            Sampler s = new Sampler();
            
            s.Add(array);

            Assert.IsTrue(Math.Abs(s.Variance - 2818.9505) < Epsilon);
            Assert.IsTrue(Math.Abs(s.StdDev - Math.Sqrt(2818.9505)) < Epsilon);
            Assert.IsTrue(Math.Abs(s.Mean - array.Average()) < Epsilon);
        }

        /// <summary>
        /// Tests if CallToRestCleansUpTheStateOfTheSampler
        /// </summary>
        [Priority(0)]
        [TestMethod]
        [TestCategory("Unit")]
        public void CallToRestCleansUpTheStateOfTheSampler()
        {
            // Addsome values into the sampler
            Sampler s = new Sampler();

            s.Add(0.1);
            s.Add(0.3);
            s.Add(-5.0);

            s.Reset();

            Assert.AreEqual(0, s.Variance);
            Assert.AreEqual(0, s.StdDev);
            Assert.AreEqual(0, s.Mean);
            Assert.AreEqual((int)0, (int)s.SampleCount);
            Assert.AreEqual(0, s.Last);
            Assert.IsTrue(s.Min > 1E100);
            Assert.IsTrue(s.Max < 1E-100);
        }
    }
}
